Serverless FrameworkでAurora Postgres + VPC Lambda + RDS Proxyをデプロイする
こんにちは、クラスメソッドの岡です。
7月にRDS ProxyがGAとなったことで、サーバーレスでのRDS実用化待ったなし!!ということで今回はServerless FrameworkでRDS Aurora Postgres、VPC Lambda、RDS Proxyをまとめてデプロイしてみたので、テンプレートの中身を紹介していこうと思います。
動作確認環境
- Python3.8.5
- Serverless Framework
- Framework Core: 2.1.1
- Plugin: 4.0.4
- SDK: 2.3.2
- Components: 3.1.3
構成
まず、LambdaとRDSを同一のVPC内に所属させます。マルチAZ構成にするためにサブネット(プライベート)を2つ作成します。
今回はLambdaとRDSを同じサブネット内に配置しています。
セキュリティグループはLambda用とRDS用の2つ作成して、RDS用のインバウンドのルールは同一VPC内からのアクセスのみを許可します。
テンプレート
リソースを追加する前にテンプレートのベースを用意しておきます。
service: name: sample-aurora-postgres plugins: - serverless-pseudo-parameters provider: name: aws region: ap-northeast-1 stage: ${opt:stage, self:custom.defaultStage} profile: ${self:custom.profiles.${opt:stage, self:custom.defaultStage}} runtime: python3.8 stackName: sample-aurora-postgres apiName: sample-aurora-postgres logRetentionInDays: 7 versionFunctions: false iamRoleStatements: - Effect: Allow Action: - secretsmanager:GetSecretValue - rds-data:* - ec2:CreateNetworkInterface - ec2:DescribeNetworkInterfaces - ec2:DeleteNetworkInterface Resource: "*" environment: ENV: ${self:custom.environments.ENV} DB_PORT: 3306 DB_HOST: !GetAtt RDSProxy.Endpoint custom: defaultStage: itg profiles: itg: sls-itg stg: sls-stg prd: sls-prd environments: ${file(./config/config.${opt:stage, self:custom.defaultStage}.yml)} secret: ${file(./config/secret/.secret.${opt:stage, self:custom.defaultStage}.yml)}
プラグイン
serverless-pseudo-parameters
はテンプレート内でアカウントIDを参照するために追加しています。
このプラグインを使うと、#{AWS::AccountId}
という記述で参照できます。
ステージとプロファイル
AWSアカウントは環境毎に分かれるケースが多いかと思います。
デプロイコマンド実行時のstageオプションで下記のように切り替えるためにテンプレートに ${opt:stage, self:custom.defaultStage}
と変数を仕込んでおきます。
$ sls deploy →開発アカウント $ sls deploy --stage=stg →検証アカウント $ sls deploy --stage=prd →本番アカウント
stageと一緒にprofileも切り替えられるよう、custom.profiles
にそれぞれのステージに相対するAWS CLIのプロファイル名を設定しておきます。
認証情報の読み込み
RDSのクラスター作成時とSecrets Managerのシークレット作成時に同一の認証情報を指定するために ./config/secret/.secret.${env}.yml
という隠しファイルを作っておきます。
USER_NAME: postgres PASSWORD: postgres
Serverless Frameworkではテンプレート内で ${file(file_name)}
と指定することで、外部ファイルのプロパティを読み込めます。
今回はローカルからデプロイするため、この様な構成にしてますが作成したシークレット情報はGitに含めないよう注意してください。
DBのポートとエンドポイント
ちなみにこちらのドキュメントにある通り、Aurora Postgres(EngineMode: provisioned)のDBクラスターのポートを指定しなかった場合はPostgresでもデフォルトで3306ポートとなります。
今回はポート指定せずに作成するので、3306ポートを共通の環境変数として設定しておきます。
DB_HOSTには後述するRDSProxyのエンドポイントを参照できるようにしておきます。
VPC関連のリソース
Serverless Frameworkと言いつつ、作成するリソースは実質ほぼcfnの記述になります。
resources: Resources: ## VPC Resource VPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/24 Tags: - { Key: Name, Value: Sample VPC } PrivateSubnetA: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: 10.0.0.0/25 AvailabilityZone: ap-northeast-1a Tags: - { Key: Name, Value: Sample Private A } PrivateSubnetC: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: 10.0.0.128/25 AvailabilityZone: ap-northeast-1c Tags: - { Key: Name, Value: Sample Private C } LambdaSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: SecurityGroup for Lambda Functions VpcId: !Ref VPC Tags: - Key: "Name" Value: "LambdaSecurityGroup" AuroraSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: SecurityGroup for Aurora VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 3306 ToPort: 3306 CidrIp: 10.0.0.0/24 Tags: - Key: "Name" Value: "AuroraSecurityGroup" DependsOn: VPC
まずはVPC関連のリソース群です。
セキュリティグループはVPC内のIPアドレス範囲の3306ポートをインバウンドで開けておきます。
RDS関連のリソース
## RDS Resource DBSubnetGroup: Type: AWS::RDS::DBSubnetGroup Properties: DBSubnetGroupDescription: "SampleDB subnet group" DBSubnetGroupName: sampledb-subnet-group SubnetIds: - !Ref PrivateSubnetA - !Ref PrivateSubnetC DBCluster: Type: AWS::RDS::DBCluster Properties: DatabaseName: SampleDB Engine: aurora-postgresql # EngineMode: serverless EngineVersion: "11.6" MasterUsername: ${self:custom.secret.USER_NAME} MasterUserPassword: ${self:custom.secret.PASSWORD} DBClusterParameterGroupName: !Ref DBClusterParameterGroup DBSubnetGroupName: !Ref DBSubnetGroup VpcSecurityGroupIds: - !Ref AuroraSecurityGroup DependsOn: DBSubnetGroup DBClusterParameterGroup: Type: AWS::RDS::DBClusterParameterGroup Properties: Description: A parameter group for aurora Family: aurora-postgresql11 Parameters: client_encoding: UTF8 DBInstance1: Type: AWS::RDS::DBInstance Properties: DBClusterIdentifier: !Ref DBCluster DBSubnetGroupName: !Ref DBSubnetGroup Engine: aurora-postgresql EngineVersion: "11.6" DBInstanceClass: db.t3.medium DependsOn: DBCluster
今回はプライマリインスタンス1台(DBInstance1)のみ作成します。ちなみにDBClusterのEngineModeをserverless
にすると、Aurora Serverlessでの起動となります。
サブネットを2つ以上指定する場合は、DBSubnetGroupを作成する必要があります。
Secrets ManagerとRDS Proxy周りのリソース
AuroraSecret: Type: AWS::SecretsManager::Secret Properties: Name: Sample/aurora SecretString: '{"username":"${self:custom.secret.USER_NAME}", "password":"${self:custom.secret.PASSWORD}"}' SecretTargetAttachment: Type: AWS::SecretsManager::SecretTargetAttachment Properties: SecretId: !Ref AuroraSecret TargetId: !Ref DBCluster TargetType: "AWS::RDS::DBCluster" DependsOn: DBCluster ProxyRole: Type: AWS::IAM::Role Properties: RoleName: sample-proxy-role AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - "rds.amazonaws.com" Action: - "sts:AssumeRole" Path: / Policies: - PolicyName: RdsProxyPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "secretsmanager:GetResourcePolicy" - "secretsmanager:GetSecretValue" - "secretsmanager:DescribeSecret" - "secretsmanager:ListSecretVersionIds" Resource: - !Ref AuroraSecret - Effect: Allow Action: - "kms:Decrypt" Resource: "arn:aws:kms:${self:provider.region}:#{AWS::AccountId}:key/*" Condition: StringEquals: kms:ViaService: "secretsmanager.${self:provider.region}.amazonaws.com" DependsOn: AuroraSecret RDSProxy: Type: AWS::RDS::DBProxy Properties: DBProxyName: SampleAuroraProxy Auth: - SecretArn: !Ref AuroraSecret VpcSecurityGroupIds: - !Ref AuroraSecurityGroup VpcSubnetIds: - !Ref PrivateSubnetA - !Ref PrivateSubnetC EngineFamily: POSTGRESQL RoleArn: !GetAtt ProxyRole.Arn DependsOn: AuroraSecret DBProxyTargetGroup: Type: AWS::RDS::DBProxyTargetGroup Properties: TargetGroupName: default DBProxyName: !Ref RDSProxy DBClusterIdentifiers: - !Ref DBCluster DependsOn: RDSProxy
ProxyRole
はRDS Proxyに設定するIAMロールです。
今回はSecrets ManagerにRDS用の認証情報を格納しているので、Secrets Managerの権限と取得後にKMSでの復号化権限をアタッチしておきます。
RDS Proxyの認証情報をSecrets Managerに保存する場合、 SecretTargetAttachment
を作成する必要があります。
また、注意点として公式ドキュメントにもある通り、DBProxyTargetGroup
のTargetGroupNameはdefault
の固定値に設定する必要があります。
VPC Lambdaの定義
functions: testFunc: name: test_func handler: src/handlers/test_func.handler description: "接続確認用" vpc: securityGroupIds: - !Ref LambdaSecurityGroup subnetIds: - !Ref PrivateSubnetA - !Ref PrivateSubnetC events: - http: path: /test method: post cors: true
VPCをLambdaに設定します。上で作成したセキュリティグループとサブネットを指定します。
デプロイ
下記コマンドで開発環境にデプロイします。
$ sls deploy
RDSのクラスターとインスタンスの生成に結構時間がかかります。